Create New Item
Item Type
File
Folder
Item Name
Search file in folder and subfolders...
Are you sure want to rename?
File Manager
/
javascript
/
canvas_gauges
:
gulpfile.js
Advanced Search
Upload
New Item
Settings
Back
Back Up
Advanced Editor
Save
/** * Canv-Gauge Dev Tasks Runner * * @author Mykhailo Stadnyk <mikhus@gmail.com> */ const gulp = require('gulp'); const usage = require('gulp-help-doc'); const browserify = require('browserify'); const source = require('vinyl-source-stream'); const buffer = require('vinyl-buffer'); const rename = require('gulp-rename'); const sourcemaps = require('gulp-sourcemaps'); const uglify = require('gulp-uglify'); const minify = require('gulp-uglify/minifier'); const uglify6 = require('uglify-js-harmony'); const rimraf = require('rimraf'); const esdoc = require('gulp-esdoc'); const eslint = require('gulp-eslint'); const gzip = require('gulp-gzip'); const KarmaServer = require('karma').Server; const gutil = require('gulp-util'); const chalk = require('chalk'); const http = require('https'); const fs = require('fs'); const selenium = require('selenium-standalone'); const mocha = require('gulp-mocha'); const cp = require('child_process'); const concat = require('gulp-concat'); const yargs = require('yargs'); const replace = require('gulp-replace'); const babel = require('gulp-babel'); const fsc = require('fs-cli'); const semver = require('semver'); const inject = require('gulp-inject-string'); const version = require('./package.json').version; const plato = require('gulp-plato'); /** * @typedef {{argv: object}} yargs * @typedef {{parse:function, stringify:function}} JSON */ let types = ['all', 'radial', 'linear']; function es6concat(type = 'all', clean = false) { let bundle = [ 'lib/polyfill.js', 'lib/vendorize.js', 'lib/EventEmitter.js', 'lib/Animation.js', 'lib/DomObserver.js', 'lib/SmartCanvas.js', 'lib/GenericOptions.js', 'lib/Collection.js', 'lib/BaseGauge.js', 'lib/drawings.js' ]; if (clean) bundle.shift(); switch (type.toLowerCase()) { case 'radial': case 'radial-gauge': case 'radialgauge': bundle.push('lib/RadialGauge.js'); break; case 'linear': case 'linear-gauge': case 'lineargauge': bundle.push('lib/LinearGauge.js'); break; default: bundle.push('lib/RadialGauge.js', 'lib/LinearGauge.js'); } return gulp.src(bundle) .pipe(concat('gauge.es6.js')) .pipe(replace(/((var|const|let)\s+.*?=\s*)?require\(.*?\);?/g, '')) .pipe(replace(/(module\.)?exports(.default)?\s+=\s*.*?\r?\n/g, '')) .pipe(replace(/export\s+(default\s+)?(GenericOptions;)?/g, '')) .pipe(replace(/%VERSION%/g, version)); } function wrap(stream) { return stream.pipe(replace(/^/, license() + '(function(ns) {')) .pipe(replace(/$/, ';typeof module !== "undefined" && Object.assign(ns, {' + 'Collection: Collection,' + 'GenericOptions: GenericOptions,' + 'Animation: Animation,' + 'BaseGauge: BaseGauge,' + 'drawings: drawings,' + 'SmartCanvas: SmartCanvas,' + 'DomObserver: DomObserver,' + 'vendorize: vendorize' + '});' + '}(typeof module !== "undefined" ? ' + 'module.exports : window));')); } function es5transpile(type = 'all', withSourceMaps = true, resolve = () => {}) { let stream = wrap(es6concat(type) .pipe(rename('gauge.es5.js')) .pipe(babel({ presets: ['es2015'], compact: false })) .on('error', function(err) { gutil.log(err); this.emit('end'); resolve(); }) .pipe(rename('gauge.min.js'))); if (withSourceMaps) { stream = stream.pipe(sourcemaps.init({ loadMaps: true })); } stream = stream.pipe(uglify({ preserveComments: (node, comment) => comment.line === 1 })); if (withSourceMaps) { stream = stream.pipe(sourcemaps.write('.')); } return stream; } function license() { let src = fs.readFileSync('./LICENSE') .toString() .split(/\r?\n/); src.pop(); return '/*!\n * ' + src.join('\n * ') + '\n *\n * @version ' + version + '\n */\n'; } /** * Builds code complexity report * * @task {complexity} */ gulp.task('complexity', done => { rimraf('complexity', () => gulp.src('lib/**/*.js') .pipe(plato('complexity', { complexity: { trycatch: true }, jshint: { options: { strict: false, browser: true, browserify: true, devel: true, expr: true, esversion: 6, laxbreak: true, laxcomma: true, sub: true } } })) .on('finish', () => done()) ); }); /** * Displays this usage information. * * @task {help} */ gulp.task('help', () => usage(gulp, { emptyLineBetweenTasks: false })); /** * Builds production packages * * @task {build:prod} * @order {5} */ gulp.task('build:prod', done => { rimraf('dist', () => { Promise.all(types.map(type => { return new Promise(resolve => { es5transpile(type, false, resolve) .pipe(gulp.dest('dist/' + type)) .on('end', () => { let pkg = JSON.parse(fs.readFileSync('./package.json')); delete pkg.devDependencies; delete pkg.scripts; delete pkg.directories; if (type !== 'all') { pkg.version += '-' + type; pkg.keywords.push(type + '-gauge'); } else { for (let i = 1; i < types.length; i++) { pkg.keywords.push(types[i] + '-gauge'); } } fs.writeFileSync('dist/' + type + '/package.json', JSON.stringify(pkg, null, 2)); fs.writeFileSync('dist/' + type + '/README.md', fs.readFileSync('README.md')); fs.writeFileSync('dist/' + type + '/LICENSE', fs.readFileSync('LICENSE')); resolve(); }); }); })).then(() => { let cmd = ''; console.log(chalk.bold.green('Production packages are now ready!')); console.log('To publish each production package, please run the ' + 'following command:'); types.reverse().forEach(type => { let v = version; let entry = type === 'linear' ? './' : '../../'; if (cmd) cmd += ' && '; cmd += 'cd ' + entry + 'dist/' + type; if (type === 'all') type = 'latest'; else v += '-' + type; cmd += ' && npm publish'; if (type !== 'latest') cmd += ' && npm dist-tag add canvas-gauges@' + v + ' ' + type; }); console.log(chalk.grey(cmd)); let dst = '../canvas-gauges-pages/download'; fsc.rm(dst + '/' + version); fsc.cp('dist', dst + '/' + version, true); fsc.rm(dst + '/latest'); let releases = fsc.ls(dst); let latest = semver.maxSatisfying(releases, '*'); fsc.cp(dst + '/' + latest, dst + '/latest'); let info = {}; releases.forEach(release => { info[release] = { name: release }; types.forEach(type => { info[release][type] = { bytes: fs.statSync(dst + '/' + release + '/' + type + '/gauge.min.js').size }; info[release][type].kb = (info[release][type].bytes / 1024) .toFixed(1); info[release][type].mb = (info[release][type].bytes / (1024 * 1024)).toFixed(1); info[release][type].gb = (info[release][type].bytes / (1024 * 1024 * 1024)).toFixed(1); }); }); info.latest = JSON.parse(JSON.stringify(info[latest])); fs.writeFileSync('../canvas-gauges-pages/_data/releases.json', JSON.stringify(info, null, 2)); done(); }); }); }); /** * Currently there is no way to minify es6 code, so this task is * temporally useless. Awaiting for support of es6 in minifiers. * * @task {build:es6} * @arg {type} build type: 'radial' - Gauge object only, 'linear' - LinearGauge object only, 'all' - everything (default) * @order {4} */ gulp.task('build:es6', done => { es6concat(yargs.argv.type || 'all', true) // .pipe(minify({ // preserveComments: (node, comment) => comment.line === 1 // }, uglify6)) .pipe(gulp.dest('.')) .on('end', () => done()); }); /** * Clean-ups files from previous build. * * @task {clean} * @order {1} */ gulp.task('clean', done => { rimraf('gauge.js', () => rimraf('gauge.min.js', () => rimraf('gauge.min.js.map', () => rimraf('gauge.min.js.gz', done)))); }); /** * Cleans docs directory * * @task {clean:docs} * @order {6} */ gulp.task('clean:docs', done => { rimraf('docs', done); }); /** * Builds and minifies es5 code * * @task {build:es5} * @arg {type} build type: 'radial' - Gauge object only, 'linear' - LinearGauge object only, 'all' - everything (default) * @order {3} */ gulp.task('build:es5', gulp.series('clean', done => { es5transpile(yargs.argv.type || 'all') .pipe(gulp.dest('.')) .on('end', () => { if (!process.env.TRVIS) { fs.writeFileSync( '../canvas-gauges-pages/' + 'assets/js/gauge.min.js', fs.readFileSync('gauge.min.js') ); fs.writeFileSync( '../canvas-gauges-pages/' + 'assets/js/gauge.min.js.map', fs.readFileSync('gauge.min.js.map') ); } done(); }); })); /** * Automatically generates API documentation for this project * from a source code. * * @task {doc} * @order {7} */ gulp.task('doc', gulp.series('clean:docs', done => { gulp.src('./lib') .pipe(esdoc({ destination: './docs', package: './package.json', title: 'HTML5 Canvas Gauges API Documentation', styles: [ './assets/styles/docs.css', ] })) .on('finish', () => { if (process.env.TRAVIS) { return ; } console.log(chalk.bold.green('Generating badge...')); let coverageDataFile = './docs/coverage.json'; let coverage = require(coverageDataFile).coverage; let color = 'green'; if (parseFloat(coverage) < 90) { color = 'red'; } let url = 'https://img.shields.io/badge/docs-' + coverage.replace(/%/g, '%25') + '-' + color + '.svg'; http.get(url, response => { if ((response instanceof Error)) { return console.error(response); } response.pipe(fs.createWriteStream('docs-coverage.svg')); }); //move to pages let target = '../canvas-gauges-pages/docs/' + version; rimraf(target, () => fs.rename('docs', target, done)); }); })); /** * Transpiles, combines and minifies JavaScript code. * * @task {build} * @arg {target} compile target, could be 'es5' (default) or 'es6' * @arg {type} build type: 'radial' - Gauge object only, 'linear' - LinearGauge object only, 'all' - everything (default) * @order {2} */ gulp.task('build', gulp.series( 'build:' + (yargs.argv.target || 'es5'), done => done())); /** * Watch for source code changes and automatically re-build * when wny of them detected. * * @task {watch} */ gulp.task('watch', () => { gulp.watch('lib/**/*.js', gulp.series('build')); }); /** * Runs gzipping for minified file. * * @task {gzip} */ gulp.task('gzip', gulp.series('build:prod', done => { Promise.all(types.map(type => new Promise(resolve => { gulp.src('dist/' + type + '/gauge.min.js') .pipe(gzip({ gzipOptions: { level: 9 } })) .pipe(gulp.dest('dist/' + type)) .on('end', resolve); }))).then(() => done()); })); /** * Performs JavaScript syntax check. * * @task {lint} */ gulp.task('lint', () => { console.log(chalk.bold.green('\nStarting linting checks...\n')); return gulp.src([ '**/*.js', '!node_modules/**', '!docs/**', '!**.min.js', '!coverage/**', '!complexity/**', '!dist/**', '!test/cjs/**' ]) .pipe(eslint()) .pipe(eslint.format()) .pipe(eslint.failAfterError()); }); /** * Runs unit tests. * * @task {test:spec} */ gulp.task('test:spec', done => { console.log(chalk.bold.green('Starting unit tests...')); new KarmaServer({ configFile: __dirname + '/karma.conf.js', singleRun: true }, exitCode => { if (!exitCode) { if (process.env.TRAVIS) { return done(); } console.log(chalk.bold.green('Generating badge...')); // create badges let rx = new RegExp( '[\u001b\u009b][[()#;?]*' + '(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?' + '[0-9A-ORZcf-nqry=><]', 'g'); let coverage = fs.readFileSync( './coverage/report/coverage.txt', { encoding: 'utf8' } ) .replace(rx, '') .split(/\r?\n/)[2] .split(':')[1] .split('(')[0] .trim(); let color = 'green'; if (parseFloat(coverage) < 90) { color = 'red'; } let url = 'https://img.shields.io/badge/coverage-' + coverage.replace(/%/g, '%25') + '-' + color + '.svg'; http.get(url, response => { if ((response instanceof Error)) { console.error(response); process.exit(0); } response .pipe(fs.createWriteStream('test-coverage.svg')) .on('finish', () => process.exit(0)); }); return done(); } process.exit(exitCode); }).start(); }); /** * Installs and starts selenium server * * @task {selenium} */ gulp.task('selenium', done => { cp.execSync('pkill -f selenium-standalone'); selenium.install({ logger: gutil.log }, err => { if (err) return done(err); selenium.start((err, child) => { if (err) return done(err); selenium.child = child; done(); }); }); }); /** * Runs end-to-end tests. * * @task {test:e2e} * * Testing concept could be following: * 1. Create various gauge configuration HTML pages * 2. Use protractor to navigate this pages * 3. On each page, depending on the gauge config and screen res/dpi * checking the proper pixels on canvas if them matching expected color * This could be checks, for example if highlight area in this pixel is * present or not, if arrow starts/ends at this point, etc. * This would be valuable checks, because if something wrong happen with * drawing, most probably expected pixels won't match the expected specs, * so we can automatically figure something is broken. */ gulp.task('test:e2e', gulp.series('selenium', done => { console.log(chalk.bold.green('\nStarting end-to-end tests...\n')); gulp.src(['test/e2e/**/*.e2e.js']) .pipe(mocha({ reporter: 'spec' })) .once('error', err => { console.error(err); cp.execSync('pkill -f selenium-standalone'); process.exit(1); }) .once('end', () => { cp.execSync('pkill -f selenium-standalone'); done(); }); })); /** * Runs all tests and checks. * * @task {test} */ gulp.task('test', gulp.series('lint', 'test:spec'/*, 'test:e2e'*/)); gulp.task('default', gulp.series('help'));